﻿//
// Copyright © MixModes Inc. 2011
//

using System;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using MixModes.Phone.Synergy.Utilities;
using MixModes.Phone.Synergy.ViewModels;
using MixModes.Phone.Synergy.Views;

namespace MixModes.Phone.Synergy.Framework
{
    /// <summary>
    /// Phone application
    /// </summary>
    public class PhoneApplication : Application
    {
        /// <summary>
        /// Occurs when settings are to be saved
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<DataStoreEventArgs> SaveSettings;

        /// <summary>
        /// Occurs when settings are to be loaded
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<DataStoreEventArgs> LoadSettings;

        /// <summary>
        /// Provides easy access to the root frame of the Phone Application.
        /// </summary>
        /// <returns>The root frame of the Phone Application.</returns>
        public PhoneApplicationFrame RootFrame { get; private set; }

        /// <summary>
        /// Gets or sets the instance.
        /// </summary>
        /// <value>The instance.</value>
        public static PhoneApplication Instance { get; private set; }

        /// <summary>
        /// Gets the dispatcher.
        /// </summary>
        public Dispatcher Dispatcher
        {
            get
            {
                return RootFrame.Dispatcher;
            }
        }

        /// <summary>
        /// Constructor for the Application object.
        /// </summary>
        public PhoneApplication()
        {
            Instance = this;

            // Global handler for uncaught exceptions. 
            UnhandledException += Application_UnhandledException;
                        
            // Phone-specific initialization
            InitializePhoneApplication();

            Startup += OnStartup;
        }        

        /// <summary>
        /// Navigates to a screen bound to the specified view model via ViewSelector and ViewBinding
        /// </summary>        
        public static void Navigate<T>() where T:ViewModelBase, new()
        {   
            Instance.Dispatcher.BeginInvoke(()=>Instance.SafeNavigate(new T()));
        }

        /// <summary>
        /// Gets a value indicating whether phone is using a dark theme
        /// </summary>
        /// <value>
        /// 	<c>true</c> if phone is using a dark theme; otherwise, <c>false</c>.
        /// </value>
        public static bool IsDarkTheme
        {
            get
            {
                Color foregroundColor = (Color)Instance.Resources["PhoneForegroundColor"];
                return foregroundColor.ToString().ToUpper() == "#FFFFFFFF";
            }
        }

        /// <summary>
        /// Registers the view selector.
        /// </summary>
        /// <param name="viewSelector">The view selector.</param>
        internal void RegisterViewSelector(ViewSelector viewSelector)
        {
            Validate.NotNull(viewSelector, "viewSelector");
            _viewSelectors.Add(new WeakReference(viewSelector));
        }

        /// <summary>
        /// Unregisters the view selector.
        /// </summary>
        /// <param name="viewSelector">The view selector.</param>
        internal void UnregisterViewSelector(ViewSelector viewSelector)
        {
            Validate.NotNull(viewSelector, "viewSelector");
            WeakReference reference = (from item in _viewSelectors where item.IsAlive && item.Target.Equals(viewSelector) select item).FirstOrDefault();
            if (reference != null)
            {
                _viewSelectors.Remove(reference);
            }
        }

        /// <summary>
        /// Navigates a page in a thread safe manner
        /// </summary>
        /// <param name="viewModel">The view model </param>
        private void SafeNavigate(ViewModelBase viewModel)
        {
            Validate.NotNull(viewModel, "viewModel");

            string viewModelTypeName = viewModel.GetType().FullName;
            string viewUri = null;

            List<WeakReference> deadReferences = new List<WeakReference>();
            foreach (WeakReference viewSelector in Instance._viewSelectors)
            {
                ViewSelector selector;
                if ((!viewSelector.IsAlive) || ((selector = viewSelector.Target as ViewSelector) == null))
                {
                    deadReferences.Add(viewSelector);
                    continue;
                }

                foreach (ViewBinding binding in selector.ViewBindings)
                {
                    if (binding.ViewModelType == viewModelTypeName)
                    {
                        Instance._currentViewModel = viewModel;
                        viewUri = binding.ViewUri;
                    }
                }
            }

            deadReferences.ForEach(item => Instance._viewSelectors.Remove(item));

            Validate.Assert<InvalidOperationException>(Instance._currentViewModel != null);
            Validate.StringNotNullOrEmpty(viewUri, "viewUri");

            Instance.RootFrame.Navigate(new Uri(viewUri, UriKind.Relative));
        }

        // Code to execute if a navigation fails
        private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // A navigation has failed; break into the debugger
                System.Diagnostics.Debugger.Break();
            }
        }

        // Code to execute on Unhandled Exceptions
        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // An unhandled exception has occurred; break into the debugger
                System.Diagnostics.Debugger.Break();
            }
        }
                
        // Do not add any additional code to this method
        private void InitializePhoneApplication()
        {
            if (_phoneApplicationInitialized)
            {
                return;
            }

            // Create the frame but don't set it as RootVisual yet; this allows the splash
            // screen to remain active until the application is ready to render.
            RootFrame = new PhoneApplicationFrame();
            RootFrame.Navigated += CompleteInitializePhoneApplication;

            // Handle navigation failures
            RootFrame.NavigationFailed += RootFrame_NavigationFailed;

            Exit += OnApplicationExit;

            // Ensure we don't initialize again
            _phoneApplicationInitialized = true;            
        }

        /// <summary>
        /// Called when application is activated
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Phone.Shell.ActivatedEventArgs"/> instance containing the event data.</param>
        private void OnActivated(object sender, ActivatedEventArgs e)
        {
            using (PersistentDataStore persistentDataStore = new PersistentDataStore())
            {
                using (TransientDataStore transientDataStore = new TransientDataStore())
                {
                    EventHandler<DataStoreEventArgs> loadSettings = LoadSettings;

                    if (loadSettings != null)
                    {
                        loadSettings(this, new DataStoreEventArgs(transientDataStore, persistentDataStore, ApplicationState.Activating));
                    }
                }
            }
        }

        /// <summary>
        /// Called when application is deactivated
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Phone.Shell.DeactivatedEventArgs"/> instance containing the event data.</param>
        private void OnDeactivated(object sender, DeactivatedEventArgs e)
        {
            using (PersistentDataStore persistentDataStore = new PersistentDataStore())
            {
                using (TransientDataStore transientDataStore = new TransientDataStore())
                {
                    EventHandler<DataStoreEventArgs> saveSettings = SaveSettings;

                    if (saveSettings != null)
                    {
                        saveSettings(this, new DataStoreEventArgs(transientDataStore, persistentDataStore, ApplicationState.Deactivating));
                    }
                }
            }
        }

        // Do not add any additional code to this method
        private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
        { 
            // Set the root visual to allow the application to render
            RootVisual = RootFrame;
                        
            if (_currentViewModel != null)
            {
                FrameworkElement element = e.Content as FrameworkElement;
                if (element != null)
                {
                    element.DataContext = _currentViewModel;
                }
                _currentViewModel = null;
            }
        }

        /// <summary>
        /// Called when application exits
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnApplicationExit(object sender, EventArgs e)
        {
            // Save application state
            using (PersistentDataStore persistentDataStore = new PersistentDataStore())
            {
                using (TransientDataStore transientDataStore = new TransientDataStore())
                {
                    EventHandler<DataStoreEventArgs> saveSettings = SaveSettings;

                    if (saveSettings != null)
                    {
                        saveSettings(this, new DataStoreEventArgs(transientDataStore, persistentDataStore, ApplicationState.Exit));
                    }
                }
            }

            _viewSelectors.Clear();
            _viewSelectors = null;
            _currentViewModel = null;
        }

        /// <summary>
        /// Called when application starts up
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.StartupEventArgs"/> instance containing the event data.</param>
        private void OnStartup(object sender, StartupEventArgs e)
        {
            PhoneApplicationService current = PhoneApplicationService.Current;
            if (current != null)
            {
                current.Activated += OnActivated;
                current.Deactivated += OnDeactivated;
            }
        }

        // Private members
        private List<WeakReference> _viewSelectors = new List<WeakReference>();
        private ViewModelBase _currentViewModel;
        private bool _phoneApplicationInitialized;
    }
}
